/*
 * Copyright 2010 Michael Nitsche
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package de.mnit.schnipsel.db;

import de.mnit.basis.daten.FilterText;
import de.mnit.basis.daten.FormatText;
import de.mnit.basis.daten.FormatZahl;
import de.mnit.basis.daten.MN;
import de.mnit.basis.daten.Mathe;
import de.mnit.basis.daten.WandleFolge;
import de.mnit.basis.daten.struktur.S_Folge;
import de.mnit.basis.daten.struktur.liste.Liste;
import de.mnit.basis.daten.struktur.liste.S_Liste;
import de.mnit.basis.daten.struktur.tabelle.S_Tabelle;
import de.mnit.basis.daten.typ.zeit.Datum;
import de.mnit.basis.daten.typ.zeit.S_Datum;
import de.mnit.basis.daten.typ.zeit.S_Zeitpunkt;
import de.mnit.basis.daten.typ.zeit.Zeitpunkt;
import de.mnit.basis.db.konstant.DBMS;
import de.mnit.basis.db.sql.SqlHilfe;
import de.mnit.basis.db.sys3e.e1_basis.A_E1_H;
import de.mnit.basis.fehler.Fehler;
import de.mnit.basis.fehler.system.F_Sys_Sicherheit;
import de.mnit.schnipsel.Anw_Schnipsel;
import de.mnit.schnipsel.swt.dialog.Dialog_Fortschritt;
import de.mnit.schnipsel.zentral.SC_Crypt;
import de.mnit.schnipsel.zentral.SC_GuiObs;

/**
 * @author Michael Nitsche
 */
public class DBZ_Schnipsel extends A_E1_H<DBManager> implements S_DBZ_Schnipsel {

	private final B_Schnipsel k=B_Schnipsel.info;


	public DBZ_Schnipsel(DBManager manager) {
		super(manager, "schnipsel");
//		db().befehl("ALTER TABLE "+tab+" ADD COLUMN "+B_Schnipsel.info.AST.gName()+" INTEGER");
//		db().befehl("ALTER TABLE "+tab+" ADD COLUMN "+B_Schnipsel.info.MUELL.gName()+" BOOLEAN DEFAULT FALSE NOT NULL");
//		db().befehl("ALTER TABLE "+tab+" ADD COLUMN "+B_Schnipsel.info.FORMAT.gName()+" LONGVARCHAR");
//		db().befehl("ALTER TABLE "+tab+" ALTER COLUMN "+B_Schnipsel.info.FORMAT.gName()+" SET DEFAULT ''");
//		db().befehl("ALTER TABLE "+tab+" ALTER COLUMN "+B_Schnipsel.info.FORMAT.gName()+" SET NOT NULL");
//		db().befehl("ALTER TABLE "+tab+" ALTER COLUMN "+B_Schnipsel.info.FORMAT.gName()+" SET DEFAULT NULL");
//		db().befehl("ALTER TABLE "+tab+" ADD COLUMN "+B_Schnipsel.info.CRYPT.gName()+" INTEGER DEFAULT 0 NOT NULL");
//		db().befehl("ALTER TABLE "+tab+" ALTER COLUMN "+B_Schnipsel.info.CRYPT.gName()+" SET DEFAULT NULL");
//		db().befehl("ALTER TABLE "+tab+" ADD COLUMN "+B_Schnipsel.info.CRYPT_TITEL.gName()+" BOOLEAN DEFAULT FALSE NOT NULL");
//		db().befehl("ALTER TABLE "+tab+" ADD COLUMN "+B_Schnipsel.info.CRYPT_INHALT.gName()+" BOOLEAN DEFAULT FALSE NOT NULL");
//		db().befehl("ALTER TABLE "+tab+" ALTER COLUMN "+B_Schnipsel.info.CRYPT_TITEL.gName()+" SET DEFAULT NULL");
//		db().befehl("ALTER TABLE "+tab+" ALTER COLUMN "+B_Schnipsel.info.CRYPT_INHALT.gName()+" SET DEFAULT NULL");
//		db().befehl("ALTER TABLE "+tab+" DROP COLUMN crypt_titel");
//		db().befehl("ALTER TABLE "+tab+" ADD COLUMN "+k.ABLAUF.gName()+" DATE");

		//Evtl:  (anpassen...)
//		ALTER TABLE `schnipsel` CHANGE `text` `text` TEXT CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL
//		ALTER TABLE `schnipsel` CHANGE `format` `format` TEXT CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL
//		ALTER TABLE `schnipsel`  DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci
//		ALTER TABLE `dbsys` CHANGE `id` `id` VARCHAR( 16 ) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL

		//Abgelaufene Artikel in den Müll befördern!
		db().befehl("UPDATE "+tab+" SET "+k.MUELL.gName()+"=TRUE WHERE "+k.ABLAUF.gName()+" IS NOT NULL AND "+k.ABLAUF.gName()+"<=NOW()");
	}

	// ÄNDERN

	public int plus(String titel, String text, String format, CRYPTLEVEL crypt) throws F_Sys_Sicherheit {
		String[] sa=ver2(/*titel,*/ text, format, /*crypt.titel,*/ crypt.inhalt);
		String titel_db= FormatText.zuDB_Text(titel);//sa[0];
		text=  sa[0];
		format=sa[1];
		String text_db= FormatText.zuDB_Text(text);
		String format_db= FormatText.zuDB_Text(format);
		S_Zeitpunkt jetzt=Zeitpunkt.neu();
		String felder="erstellt,geaendert,titel,text,format,klicks,crypt_inhalt"; //,crypt_titel
		String daten ="'"+jetzt.zuTextEDV()+"','"+jetzt.zuTextEDV()+"',"+titel_db+","+text_db+","+format_db+",0,"+crypt.inhalt; //,"+crypt.titel+"
		b("INSERT INTO schnipsel ("+felder+") VALUES ("+daten+")");
		int id= db().gibInt("SELECT "+SqlHilfe.letzteID(m().gDBMS())+" FROM schnipsel");
//		return db().gibInt("SELECT id FROM schnipsel WHERE text="+text+" AND erstellt='"+jetzt.zuTextEDV()+"'");
		return id;
	}

	public void aendern(int id, String titel, String text, String format, CRYPTLEVEL crypt, boolean geaendert) throws F_Sys_Sicherheit {
		String[] sa=ver2(/*titel,*/ text, format, /*crypt.titel,*/ crypt.inhalt);
		String titel_db= FormatText.zuDB_Text(titel);//sa[0];
		text=  sa[0];
		format=sa[1];
		String text_db= FormatText.zuDB_Text(text);
		String format_db= FormatText.zuDB_Text(format);
		S_Zeitpunkt jetzt=Zeitpunkt.neu();
		String sg=geaendert ? ",geaendert='"+jetzt.zuTextEDV()+"' " : " ";
		b("UPDATE schnipsel SET "
				+"titel="+titel_db+","
				+"text="+text_db+","
				+"format="+format_db+","
//				+"crypt_titel="+crypt.titel+","
				+"crypt_inhalt="+crypt.inhalt+""
				+sg
				+"WHERE id="+id);
	}

	public void sVerschluesselt(int id, CRYPTLEVEL crypt) throws F_Sys_Sicherheit {
		B_Schnipsel s=this.gEintragB(id);
		aendern(id, s.TITEL.g(), s.TEXT.g(), s.FORMAT.g(), crypt, false);
	}

	public void importiere(B_Schnipsel s) { //Problem: Crypt-Inhalte von jemand anderem importiert!!! >> Dann ist der Pass immer falsch!
		String felder=SqlHilfe.zuSQLSpalten(k.ID, k.ERSTELLT, k.GEAENDERT, k.TITEL, k.TEXT, k.FORMAT, k.KLICKS,
			k.AST, k.MUELL, k.CRYPT_INHALT, k.ABLAUF);
		int id=s.ID.g();
		String werte=""
			+id+","
			+"'"+s.ERSTELLT.g().zuTextEDV()+"',"
			+"'"+s.GEAENDERT.g().zuTextEDV()+"',"
			+FormatText.zuDB_Text(s.TITEL.g())+","
			+FormatText.zuDB_Text(s.TEXT.g())+","
			+FormatText.zuDB_Text(s.FORMAT.g())+","
			+s.KLICKS.g()+","
			+s.AST.g()+","
			+s.MUELL.g()+","
			+s.CRYPT_INHALT.g()+","
			+s.ABLAUF.g()+"";
		b("INSERT INTO schnipsel ("+felder+") VALUES ("+werte+")");
	}

	public void leeren() {
		b("DELETE FROM "+tab);
	}

	public void minus(int id, boolean loeschen) {
		if(loeschen)
			b("DELETE FROM schnipsel WHERE id="+id);
		else
			b("UPDATE schnipsel SET muell=TRUE WHERE id="+id);
	}

	public void sAblauf(int id, S_Datum ablauf) {
		String datum= ablauf==null ? "NULL" : "'"+ablauf.zuTextEDV()+"'";
		b("UPDATE "+tab+" SET ablauf="+datum+" WHERE id="+id);
	}

	// LESEN

	public Integer gAst(int id) {
		return db().gibWInteger("SELECT ast FROM schnipsel WHERE id="+id);
	}

	public B_Schnipsel gEintragB(int id) throws F_Sys_Sicherheit {
		iKlick(id);
		String felder="erstellt,geaendert,titel,text,format,klicks,ast,muell,id,crypt_inhalt,ablauf"; //,crypt_titel
		S_Liste<String> zeile=db().gibZeile("SELECT "+felder+" FROM schnipsel WHERE id="+id);
//		boolean crypt_titel =Boolean.parseBoolean(""+zeile.gib(10));
		boolean crypt_inhalt=Boolean.parseBoolean(""+zeile.gib(10)); //11
		B_Schnipsel b=new B_Schnipsel();
		b.ERSTELLT.s(Zeitpunkt.neu(zeile.gib(1)));
		b.GEAENDERT.s(Zeitpunkt.neu(zeile.gib(2)));
		String[] sa=ent(/*zeile.gib(3),*/ zeile.gib(4), zeile.gib(5), /*crypt_titel,*/ crypt_inhalt);
		b.TITEL.s(zeile.gib(3));//.s(sa[0]);
		b.TEXT.s(sa[0]);
		b.FORMAT.s(sa[1]);
		b.KLICKS.s(Integer.parseInt(""+MN.beiNull(zeile.gib(6), 0)));
		b.AST.s(Integer.parseInt(""+MN.beiNull(zeile.gib(7), 0)));
		b.MUELL.s(Boolean.parseBoolean(""+zeile.gib(8)));
		b.ID.s(Integer.parseInt(zeile.gib(9)));
//		b.CRYPT_TITEL.s(crypt_titel);
		b.CRYPT_INHALT.s(crypt_inhalt);
		b.ABLAUF.s(zeile.gib(11)==null ? null : Datum.neu(zeile.gib(11)));
		return b;
	}

	private void iKlick(int id) {
		if(!Anw_Schnipsel.DEBUG_DATEN && Anw_Schnipsel.DEBUG_AUSGABE)
			return;
		String sql="UPDATE "+tab+" SET klicks=klicks+1 WHERE id="+id;
		db().befehl(sql);
//		int klicks=db().gibInt("SELECT klicks FROM "+tab+" WHERE id="+id);
	}

	public S_Folge<B_Schnipsel> gAlleB(boolean crypt, Integer ordner) throws F_Sys_Sicherheit {
		String sordner= ordner==null
			? ""
			: " AND ast IN ("+WandleFolge.zuText(",", DBManager.baum().gUnterOrdner(ordner, true))+")";
		String sql2=crypt ? "" : " WHERE "+k.CRYPT_INHALT.gName()+"!=TRUE"+sordner;
		S_Folge<Integer> ids=db().gibSpalteInt("SELECT id FROM "+tab + sql2);
		S_Liste<B_Schnipsel> erg=Liste.neu();
		for(int id : ids)
			erg.plus(gEintragB(id));
		return erg;
	}

	/** Titel, ID **/
	public S_Tabelle<String> gChronik(int anzahl) {
		String felder=SqlHilfe.zuSQLSpalten(k.TITEL, k.ID);
		return db().gib("SELECT "+felder+" FROM "+tab+" ORDER BY "+k.GEAENDERT.gName()+" DESC LIMIT "+anzahl);
	}

	private static final String tab_felder="titel,id,klicks,crypt_inhalt"; //,crypt_titel
	private static final String tab_sort=" ORDER BY UPPER(titel)"; //TODO Case und Umlaute!!!

	public S_Tabelle<String> gTabSuche(String suche) {
		return iTabSuche(tab_felder, suche);
	}

	public S_Tabelle<String> gTabLinkDialog(String suche) {
		return iTabSuche("titel,id", suche); //,crypt_titel
	}

	private S_Tabelle<String> iTabSuche(String felder, String suche) {
		String abfrage="SELECT "+felder+" FROM schnipsel WHERE muell=FALSE";
		if(suche !=null && suche.length()>0) {
			suche=FilterText.kuerzeDoppelte(' ', suche);
			String[] liste=suche.split(" ");
			for(int p=0; p<liste.length; p++) {
				abfrage+=" AND";
				String s=liste[p];
				s=FormatText.zuDB_Text('%'+s.toUpperCase()+'%');
//				abfrage+=" (( crypt_titel=FALSE AND UPPER(titel) LIKE "+s+") OR (crypt_inhalt=FALSE AND UPPER(text) LIKE "+s+" ))";
				abfrage+=" (UPPER(titel) LIKE "+s+" OR (crypt_inhalt=FALSE AND UPPER(text) LIKE "+s+" ))";
			}
		}
		abfrage+=tab_sort;
		return this.db().gib(abfrage);
	}

	public S_Tabelle<String> gTabAst(Integer astId) {
		String abfrage="SELECT "+tab_felder+" FROM schnipsel WHERE muell=FALSE";
		if(astId !=null)
			abfrage+=" AND ast="+astId;
		else
			abfrage+=" AND ast IS NULL";
		abfrage+=tab_sort;
		return this.db().gib(abfrage);
	}

	public S_Tabelle<String> gTabMuell() {
		String abfrage="SELECT "+tab_felder+" FROM schnipsel WHERE muell=TRUE";
		abfrage+=tab_sort;
		return this.db().gib(abfrage);
	}

	// K_Liste2

	private static final String tab_felder2="titel,'','',id,klicks,crypt_inhalt,geaendert"; //,crypt_titel
	private static final int tabSpalteDatumAnzeige=3;

	public S_Tabelle<String> gTabSuche2(String suche) {
		return iFormat2(iTabSuche(tab_felder2, suche));
	}

	public S_Tabelle<String> gTabAst2(Integer astId) {
		String abfrage="SELECT "+tab_felder2+" FROM schnipsel WHERE muell=FALSE";
		if(astId !=null)
			abfrage+=" AND ast="+astId;
		else
			abfrage+=" AND ast IS NULL";
		abfrage+=tab_sort;
		return iFormat2(this.db().gib(abfrage));
	}

	public S_Tabelle<String> gTabMuell2() {
		String abfrage="SELECT "+tab_felder2+" FROM schnipsel WHERE muell=TRUE";
		abfrage+=tab_sort;
		return iFormat2(this.db().gib(abfrage));
	}

	private S_Tabelle<String> iFormat2(S_Tabelle<String> tab) {
		for(int z=1; z<=tab.gLaenge(); z++) {
			String dbDatum=tab.gib(7, z);
			String datum=dbDatum.replaceFirst("^([0-9]+)-([0-9]+)-([0-9]+) ([0-9]+):([0-9]+):.*$", "$3.$2.$1 $4:$5");
			tab.setze(tabSpalteDatumAnzeige, z, datum);

			String dbKlicks=tab.gib(5, z);
			tab.setze(5, z, FormatZahl.breite(10, dbKlicks));
		}
		return tab;
	}

	// .....

	public void sBaumZweig(int id, int astId, boolean muell) {
		Integer astId2= astId<0 ? null : astId;
		b("UPDATE schnipsel SET ast="+astId2+" WHERE id="+id);
		b("UPDATE schnipsel SET muell="+muell+" WHERE id="+id);
	}

	public void entferneBaumZweig(int ast_id) {
		b("UPDATE schnipsel SET ast=null WHERE ast="+ast_id);
	}

	public int gAnzahlGesamt(boolean muell) {
		return this.db().gibInt("SELECT COUNT(id) FROM schnipsel WHERE muell="+muell);
	}

	public S_Tabelle<String> gTabExport() {
		return gTabExport(null);
	}

	public S_Tabelle<String> gTabExport(Integer astId) {
		String felder=SqlHilfe.zuSQLSpalten(k.ID, k.ERSTELLT, k.GEAENDERT, k.TITEL,
			k.TEXT, k.FORMAT, k.KLICKS, k.AST, k.MUELL, k.CRYPT_INHALT, k.ABLAUF);
		String sql="SELECT "+felder+" FROM "+tab;
		if(astId!=null)
			sql+=" WHERE "+k.AST.gName()+"="+astId;
		return this.db().gib(sql);
	}

	public void verschluesseln(CRYPTLEVEL level) throws F_Sys_Sicherheit {
		S_Folge<Integer> ids=db().gibSpalteInt("SELECT id FROM schnipsel");
		Dialog_Fortschritt d=new Dialog_Fortschritt("Aktualisierung");
		d.oeffnen(SC_GuiObs.i().hauptfenster());
		d.setze(0, Mathe.max(1, ids.gLaenge()));
		for(int id : ids) {
			sVerschluesselt(id, level);
			d.weiter();
		}
		d.schliessen();
		SC_GuiObs.i().liste().aktualisieren();
	}

	// INTERN

	protected void erstellen() {
		DBMS dbms=this.m().gDBMS();
		if(dbms==DBMS.HSQLDB) {
//			NeuTab_HSqlDB tab=new NeuTab_HSqlDB(this.m().direkt(), this.tab);
//			tab.spaltenErstellen(B_Schnipsel.info);
			this.m().direkt().befehl("CREATE TABLE schnipsel(" +
				"id INTEGER GENERATED BY DEFAULT AS IDENTITY(START WITH 0) NOT NULL PRIMARY KEY," +
				"erstellt TIMESTAMP NOT NULL," +
				"geaendert TIMESTAMP NOT NULL," +
				"titel VARCHAR(256) NOT NULL," +
				"text LONGVARCHAR NOT NULL," +
				"format LONGVARCHAR NOT NULL," +
				"klicks INTEGER NOT NULL," +
				"ast INTEGER," +
				"muell BOOLEAN DEFAULT FALSE NOT NULL," +
				"crypt_inhalt BOOLEAN NOT NULL," +
				"ablauf DATE)");
			this.b("CREATE INDEX i_geaendert ON schnipsel (geaendert DESC)");
			this.b("CREATE INDEX i_muell ON schnipsel (muell)");
			this.b("CREATE INDEX i_ast ON schnipsel (muell,ast,titel)");
			this.b("CREATE INDEX i_text ON schnipsel (muell,titel,crypt_inhalt,text)");
		}
		else if(dbms==DBMS.MYSQL) {
			this.m().direkt().befehl("CREATE TABLE schnipsel(" +
				"id INTEGER NOT NULL AUTO_INCREMENT," +
				"erstellt TIMESTAMP NOT NULL,"+
				"geaendert TIMESTAMP NOT NULL,"+ //Nicht On_Update_Current_Timestamp!!!
				"titel VARCHAR(256) NOT NULL,"+
				"text TEXT NOT NULL,"+
				"format TEXT NOT NULL," +
				"klicks INTEGER NOT NULL,"+
				"ast INTEGER,"+
				"muell BOOLEAN DEFAULT FALSE NOT NULL,"+
				"crypt_inhalt BOOLEAN NOT NULL,"+
				"ablauf DATE,"+
				"PRIMARY KEY(id))");
			//Optimieren mit einem FullTextIndex und Match Against!
			this.b("CREATE INDEX i_geaendert ON schnipsel (geaendert DESC)");
			this.b("CREATE INDEX i_muell ON schnipsel (muell)");
			this.b("CREATE INDEX i_ast ON schnipsel (muell,ast,titel)");
			this.b("CREATE INDEX i_text ON schnipsel (muell,titel,crypt_inhalt,text)");
		}
		else Fehler.sonstige.da_ToDo(dbms);
	}

	private String[] ver2(/*String titel,*/ String text, String format, /*boolean crypt_titel,*/ boolean crypt_inhalt) throws F_Sys_Sicherheit {
		String[] sa=SC_Crypt.i().verschluesseln(/*titel,*/ text, format, /*crypt_titel,*/ crypt_inhalt);
//		if(sa[0]!=null) sa[0]=FormatText.zuDB_Text(sa[0]);
//		if(sa[1]!=null) sa[1]=FormatText.zuDB_Text(sa[1]);
//		if(sa[2]!=null) sa[2]=FormatText.zuDB_Text(sa[2]);
		return sa;
	}

	private String[] ent(/*String titel,*/ String text, String format, /*boolean crypt_titel,*/ boolean crypt_inhalt) throws F_Sys_Sicherheit {
		return SC_Crypt.i().entschluesseln(/*titel,*/ text, format, /*crypt_titel,*/ crypt_inhalt);
	}

}
